package com.chianghao.rockframewrok.auth.web.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.chianghao.rockframewrok.auth.Config;
import com.chianghao.rockframewrok.auth.cache.model.Resource;
import com.chianghao.rockframewrok.auth.cache.model.Role;
import com.chianghao.rockframewrok.auth.cache.model.User;
import com.chianghao.rockframewrok.auth.cache.model.resource.PathResource;
import com.chianghao.rockframewrok.auth.core.AbstractAuthorizing;
import com.chianghao.rockframewrok.auth.core.AuthResult;
import com.chianghao.rockframewrok.auth.core.token.ITokenHandle;
import com.chianghao.rockframewrok.auth.core.token.model.Token;
import com.chianghao.rockframewrok.auth.web.PathMatcherUtils;
import com.chianghao.rockframewrok.auth.web.RequestContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

import com.alibaba.fastjson.JSON;
import com.chianghao.rockframewrok.auth.cache.IAuthCache;
import com.chianghao.rockframewrok.auth.enums.AuthCheckResult;
import com.chianghao.rockframewrok.auth.enums.ResourceType;
import com.chianghao.rockframewrok.auth.enums.TokenCheckResult;
import com.chianghao.rockframewrok.core.exception.ErrorInfo;
import com.chianghao.rockframewrok.core.exception.HaoException;
import com.chianghao.rockframewrok.core.web.R;

public class AuthFilter extends AbstractAuthorizing<User, PathResource> implements Filter {

	Logger log  = LoggerFactory.getLogger(this.getClass());
	
	
	ITokenHandle<? extends Token> tokenHandle;
	
	
	public AuthFilter(IAuthCache authcache,ITokenHandle<? extends Token> tokenHandle) {
		super(authcache);
		this.tokenHandle = tokenHandle;
	}
	
	@Override
	public boolean checkResourceAccent(User user, PathResource resource) {
		Set<PathResource> allResource =  this.authcache.getResource(ResourceType.url).stream().map(item->(PathResource)item).collect(Collectors.toSet());
		Set<PathResource> matchedMethodResource = allResource.stream().filter(item->{
			if(item.getMethod().toUpperCase().equals(resource.getMethod().toUpperCase())) {
				return true;
			}else if(item.getMethod().equals("ALL")){
				return true;
			}else {
				return false;
			}
		}).collect(Collectors.toSet());
		
		Resource tagResource = PathMatcherUtils.lookupWhiteListInfo(resource.getUrl(),matchedMethodResource);
		if(tagResource==null) {
			// 未登记的资源，默认所有角色都能访问
			return true;
		}else {
			// 如果登记了则校验角色
			Set<Role> userRole = this.authcache.getUserRole(user);
			Set<Resource> allUserResource = this.getUserAllResource(user, ResourceType.url);
			for(Role r:userRole) {
				Set<Resource> roleResource = this.authcache.getRoleResouce(r);
				if(roleResource!=null&&roleResource.size()>0) {
					allUserResource.addAll(roleResource);
				}
			}
			// 将用户拥有的所有资源和目标资源匹配，如果有则返回true，否则返回false
			if(allUserResource.contains(tagResource)) {
				return true;
			}else {
				return false;
			}
		}
    }
	
	
	@Override
	public AuthResult auth(User user, PathResource resource) {
		String method = resource.getMethod();
		String url    = resource.getUrl();
		if(resource.getUrl().startsWith("/")) {
            url =  url.substring(1);
        }
        final String _method = method.toUpperCase();
        log.info("校验接口权限==========method:{},url:{},token:{}============",method,url);
        AuthResult out = new AuthResult();
        if(StringUtils.isEmpty(method)) {
            out.setResule(AuthCheckResult.authenInfoError);
            out.setFailMessage("校验权限方法参数method为空");
            return out;
        }
        if(StringUtils.isEmpty(url)) {
            out.setResule(AuthCheckResult.authenInfoError);
            out.setFailMessage("校验权限方法参数url为空");
            return out;
        }
		Collection<PathResource> whiteList  = this.authcache.getAllWhileList(ResourceType.url);


		Resource white = null;

        if(whiteList!=null){
			// method赛选白名单
			List<PathResource> _whilteList  = whiteList.stream().filter(item->{
				if(item.getMethod().equals("ALL")||item.getMethod().toUpperCase().equals(_method)) {
					return true;
				}else {
					return false;
				}
			}).collect(Collectors.toList());
			// 根据路径赛选白名单
			white = PathMatcherUtils.lookupWhiteListInfo(url,_whilteList);
		}


        if(white!=null) {
            //无需要验证登陆
            out.setResule(AuthCheckResult.ok);
            return out;
        }
		// 有无登录赛选，user为null表示未登录或者登录信息有无
        if(user==null) {
			 // (1);
        	 out.setResule(AuthCheckResult.noUser);
             out.setFailMessage("auth is failed");
             return out;
        }
        
		// 权限赛选
		if(!checkResourceAccent(user,resource)) {
			out.setResule(AuthCheckResult.noPermission);
    		out.setFailMessage("无此接口的权限");
            return out;
		}
		out.setResule(AuthCheckResult.ok);
		return out;
	}

	
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		String _authContentTypeName = filterConfig.getInitParameter("AuthContentTypeName");
		if (StringUtils.isNotEmpty(_authContentTypeName)) {
			Config.authContentTypeName = _authContentTypeName;
		}
		String outTimeString = filterConfig.getInitParameter("outTime");
		if (StringUtils.isNotEmpty(outTimeString)) {
			Config.tokenOutTime.getAndSet(Long.parseLong(outTimeString));
		}
	}

	private boolean isSwaggerApi(String uri) {
		// swagger-resources/
		// /csrf
		if(uri.equals("/swagger-ui.html")
				||uri.startsWith("/webjars/")
				||uri.startsWith("/swagger-resources")
				||uri.startsWith("/v2/api-docs")
				||uri.startsWith("/csrf")
				) {
			return true;
		}
		return false;
	}
	
	
	
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		String tokenContent = req.getHeader(Config.authContentTypeName);
		
		
		User user  = null;
		TokenCheckResult tokenCheckResule = TokenCheckResult.ok;	
		if(StringUtils.isNotEmpty(tokenContent)) {
			Token  token  = null;
			try {
				token = tokenHandle.parseToken(tokenContent);
			}catch(Exception e){
				tokenCheckResule = TokenCheckResult.tokenError;
			}
			if(token!=null) {
				if(token.getUserKey()==null) {
					tokenCheckResule = TokenCheckResult.noUser;
				}
				
				RequestContext.setUserId(token.getUserKey());
				RequestContext.setToken(token);
				
				String userKey  = token.getUserKey();
				user = new User() {
					@Override
					public String getKey() {
						return userKey;
					}
				};
			}
		}else {
			tokenCheckResule = TokenCheckResult.noToken;
		}
		
		PathResource pathResource = new PathResource(req.getMethod(), req.getRequestURI(), null);
		if(!isSwaggerApi(req.getRequestURI())) {
			AuthResult checkPermissionOutDto = this.auth(user,pathResource);
			
			// 如果校验结果不是OK
			if (!checkPermissionOutDto.getResule().equals(AuthCheckResult.ok)) {
				log.info("[认证授权失败],rul =====>{}" ,req.getRequestURI());
				resp.setCharacterEncoding("UTF-8");
				resp.setContentType("application/json; charset=utf-8");
				if(checkPermissionOutDto.getResule().equals(AuthCheckResult.noUser)) {
					switch(tokenCheckResule) {
					case ok:
						break;
					default:
						break;
					}
					resp.setStatus(HttpStatus.UNAUTHORIZED.value());
				}else if(checkPermissionOutDto.getResule().equals(AuthCheckResult.noPermission)) {
					resp.setStatus(HttpStatus.FORBIDDEN.value());
				}else {
					resp.setStatus(HttpStatus.BAD_REQUEST.value());
				}
				PrintWriter out = null;
				try {
					out = response.getWriter();
					out.append(JSON.toJSONString(R.error(new HaoException(ErrorInfo.unauthorization, checkPermissionOutDto.getFailMessage()))));
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					if (out != null) {
						out.close();
					}
				}
				return;
			}
			
		}
		chain.doFilter(request, response);
	}

	
	




}
